﻿namespace HyperLinq
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	/// <summary>
	/// Provides extensions methods to 2-dimensional arrays.
	/// </summary>
	public static class ExtensionsTo2DArrays
	{
		#region Conversion
		/// <summary>
		/// Returns the elements of an array at specified positions as an enumerable.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="predicate"></param>
		/// <returns></returns>
		public static IEnumerable<T> AsEnumerable<T>(this T[,] source, Func<int, int, bool> predicate)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (predicate == null)
				throw new ArgumentNullException("predicate");

			int width = source.GetLength(0), height = source.GetLength(1);

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					if (predicate(x, y))
						yield return source[x, y];
		}
		/// <summary>
		/// Returns the array as an enumerable.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static IEnumerable<T> AsEnumerable<T>(this T[,] source)
		{
			return source.AsEnumerable((_1, _2) => true);
		}

		/// <summary>
		/// Converts a two dimensional array into a single dimensional and projects each element into a new form.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="selector"></param>
		/// <returns></returns>
		public static TResult[] To1DArray<T, TResult>(this T[,] source, Func<T, TResult> selector)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (selector == null)
				throw new ArgumentNullException("selector");

			int width = source.GetLength(0), height = source.GetLength(1);

			TResult[] result = new TResult[width * height];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[x * width + y] = selector(source[x, y]);

			return result;
		}
		/// <summary>
		/// Converts a two dimensional array into a single dimensional one.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static T[] To1DArray<T>(this T[,] source)
		{
			return source.To1DArray(e => e);
		}

		/// <summary>
		/// Converts a single dimensional array to a two dimensional by splitting it with specified column lengths.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static T[,] To2DArray<T>(this T[] source, int length)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (length <= 0)
				throw new ArgumentOutOfRangeException("length");

			T[,] result = new T[source.Length / length, length];

			for (int i = 0; i < source.Length; i++)
				result[i % length, i / length] = source[i];

			return result;
		}

		/// <summary>
		/// Converts a multidimensional array to a jagged array.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static T[][] ToJaggedArray<T>(this T[,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			
			int width = source.GetLength(0), height = source.GetLength(1);

			T[][] result = new T[width][];

			for (int x = 0; x < width; x++)
			{
				result[x] = new T[height];

				for (int y = 0; y < height; y++)
					result[x][y] = source[x, y];
			}

			return result;
		}

		/// <summary>
		/// Converts a jagged array to a multidimensional array.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static T[,] To2DArray<T>(this T[][] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			T[,] result = new T[source.Length, source.Max(a => a.Length)];

			for (int x = 0; x < source.Length; x++)
			{
				for (int y = 0; y < source[x].Length; y++)
					result[x, y] = source[x][y];
			}

			return result;
		}

		/// <summary>
		/// Enumerates the columns of a two dimensional array.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static IEnumerable<T[]> AsColumns<T>(this T[,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			for (int y = 0; y < height; y++)
			{
				T[] column = new T[height];

				for (int x = 0; x < width; x++)
					column[x] = source[x, y];

				yield return column;
			}
		}
		
		/// <summary>
		/// Enumerates the rows of a two dimensional array.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static IEnumerable<T[]> AsRows<T>(this T[,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			for (int x = 0; x < width; x++)
			{
				T[] row = new T[height];

				for (int y = 0; y < height; y++)
					row[y] = source[x, y];

				yield return row;
			}
		}
		#endregion

		#region Mapping
		/// <summary>
		/// Projects each element of an array into a new form.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="selector">A transform function to apply to each element.</param>
		/// <returns></returns>
		public static TResult[,] Map<T, TResult>(this T[,] source, Func<T, TResult> selector)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (selector == null)
				throw new ArgumentNullException("selector");

			int width = source.GetLength(0), height = source.GetLength(1);

			TResult[,] result = new TResult[width, height];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[x, y] = selector(source[x, y]);

			return result;
		}
		/// <summary>
		/// Projects each element of an array into a new form.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source"></param>
		/// <param name="selector">A transform function to apply to each element.</param>
		/// <returns></returns>
		public static TResult[,] Map<T, TResult>(this T[,] source, Func<T[,], int, int, TResult> selector)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (selector == null)
				throw new ArgumentNullException("selector");

			int width = source.GetLength(0), height = source.GetLength(1);

			TResult[,] result = new TResult[width, height];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[x, y] = selector(source, x ,y);

			return result;
		}

		/// <summary>
		/// Replaces every element that matches a predicate to another one.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="predicate">A predicate function used to match elements.</param>
		/// <param name="replacement">The element to replace matched elements.</param>
		/// <returns></returns>
		public static T[,] Replace<T>(this T[,] source, Func<T, bool> predicate, T replacement)
		{
			return source.Map(e => predicate(e) ? replacement : e);
		}
		/// <summary>
		/// Replaces each occurrences of an element to another one.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="subject">The element to be replaced.</param>
		/// <param name="replacement">The element to replace matched elements.</param>
		/// <returns></returns>
		public static T[,] Replace<T>(this T[,] source, T subject, T replacement) where T : IComparable<T>
		{
			return source.Replace(e => e.Equals(subject), replacement);
		}

		/// <summary>
		/// Replaces each element that does not match a specified predicate with the default element.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="predicate">A predicate function used to filter the elements.</param>
		/// <returns></returns>
		public static T[,] Where<T>(this T[,] source, Func<T, bool> predicate)
		{
			return source.Map(e => predicate(e) ? e : default(T));
		}
		#endregion

		#region Subarrays
		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <param name="width"></param>
		/// <param name="height"></param>
		/// <returns></returns>
		public static T[,] Crop<T>(this T[,] source, int x, int y, int width, int height)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (x <= 0)
				throw new ArgumentOutOfRangeException("x");

			if (y <= 0)
				throw new ArgumentOutOfRangeException("y");

			int sourceWidth = source.GetLength(0), sourceHeight = source.GetLength(1);

			if (x > width - 2)
				throw new ArgumentOutOfRangeException("x");

			if (y > height - 2)
				throw new ArgumentOutOfRangeException("y");

			if (x + width > sourceWidth - 1)
				throw new ArgumentOutOfRangeException("width");

			if (y + height > sourceHeight - 1)
				throw new ArgumentOutOfRangeException("height");

			T[,] result = new T[width, height];

			for (int i = 0; i < width; i++)
				for (int j = 0; j < height; j++)
					result[i, j] = source[x + i, y + j];

			return result;
		}

		/// <summary>
		/// Resizes an array.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="newWidth">The new width of the array.</param>
		/// <param name="newHeight">The new height of the array.</param>
		/// <param name="paddingElement">The padding element used to fill up out of bounds locations.</param>
		/// <returns></returns>
		public static T[,] Resize<T>(this T[,] source, int newWidth, int newHeight, T paddingElement = default(T))
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (newWidth <= 0)
				throw new ArgumentOutOfRangeException("x");

			if (newHeight <= 0)
				throw new ArgumentOutOfRangeException("y");

			int width = source.GetLength(0), height = source.GetLength(1);

			T[,] result = new T[newWidth, newHeight];

			for (int x = 0; x < newWidth; x++)
				for (int y = 0; y < newHeight; y++)
					result[x, y] = (x < width && y < height) ? source[x, y] : paddingElement;

			return result;
		}

		/// <summary>
		/// Returns the minor matrix of the array specified by a column and a row index.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="x">The column index to skip.</param>
		/// <param name="y">The row index to skip.</param>
		/// <returns></returns>
		public static T[,] MinorMatrix<T>(this T[,] source, int x, int y)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			if (x < 0 || x >= width)
				throw new ArgumentOutOfRangeException("x");

			if (y < 0 || y >= height)
				throw new ArgumentOutOfRangeException("y");

			T[,] result = new T[width - 1, height - 1];

			for (int i = 0; i < width; i++)
				for (int j = 0; j < height; j++)
				{
					if (i == x || j == y)
						continue;

					result[i - (i > x ? 1 : 0), j - (j > y ? 1 : 0)] = source[i, j];
				}

			return result;
		}
		#endregion

		#region Grouping
		/// <summary>
		/// Splits an array into subarrays by a specified group width and height.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="groupWidth">The width of the subarrays.</param>
		/// <param name="groupHeight">The height of the subarrays.</param>
		/// <returns></returns>
		public static T[,][,] Split<T>(this T[,] source, int groupWidth, int groupHeight)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (groupWidth <= 0)
				throw new ArgumentOutOfRangeException("groupWidth");

			if (groupHeight <= 0)
				throw new ArgumentOutOfRangeException("groupHeight");

			int width = source.GetLength(0), height = source.GetLength(1);

			if (width % groupWidth != 0)
				throw new ArgumentException("Width of the source array must be an integer multiple of group width.");

			if (height % groupHeight != 0)
				throw new ArgumentException("Height of the source array must be an integer multiple of group height.");

			T[,][,] result = new T[width / groupWidth, height / groupHeight][,];

			for (int x = 0; x < width; x += groupWidth)
				for (int y = 0; y < height; y += groupHeight)
				{
					int gx = x / groupWidth,
						gy = y / groupHeight;

					result[gx, gy] = source.Crop(x, y, groupWidth, groupHeight);
				}

			return result;
		}
		#endregion

		#region Combination
		/// <summary>
		/// Merges two arrays by using the specified predicate function.
		/// </summary>
		/// <typeparam name="T1"></typeparam>
		/// <typeparam name="T2"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The second array to merge.</param>
		/// <param name="resultSelector">A function that specifies how to merge the elements from the two arrays.</param>
		/// <returns></returns>
		public static TResult[,] Combine<T1, T2, TResult>(this T1[,] source, T2[,] second, Func<T1, T2, TResult> resultSelector)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			if (resultSelector == null)
				throw new ArgumentNullException("resultSelector");

			int width = source.GetLength(0), height = source.GetLength(1);

			if (second.GetLength(0) != width)
				throw new ArgumentOutOfRangeException("The width of the second array must match the width of the source array.");

			if (second.GetLength(0) != height)
				throw new ArgumentOutOfRangeException("The height of the second array must match the height of the source array.");

			TResult[,] result = new TResult[width, height];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[x, y] = resultSelector(source[x, y], second[x, y]);

			return result;
		}

		/// <summary>
		/// Concatenates the elements of a second array horizontally.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The second array to concatenate.</param>
		/// <returns></returns>
		public static T[,] ConcatHorizontally<T>(this T[,] source, T[,] second)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			int width = source.GetLength(0), height = source.GetLength(1);

			int secondHeight = second.GetLength(1);

			if (height != secondHeight)
				throw new ArgumentException("The height of the second array must match the height of the source array.");

			int secondWidth = second.GetLength(0);

			T[,] result = new T[width + secondWidth, height];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[x, y] = source[x, y];

			for (int x = 0; x < secondWidth; x++)
				for (int y = 0; y < height; y++)
					result[secondWidth + x, y] = second[x, y];

			return result;
		}
		/// <summary>
		/// Concatenates the elements of a second array vertically.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The second array to concatenate.</param>
		/// <returns></returns>
		public static T[,] ConcatVertically<T>(this T[,] source, T[,] second)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			int width = source.GetLength(0), height = source.GetLength(1);

			int secondWidth = second.GetLength(0);

			if (width != secondWidth)
				throw new ArgumentOutOfRangeException("The width of the second array must match the width of the source array.");

			int secondHeight = second.GetLength(1);

			T[,] result = new T[width, height + secondHeight];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[x, y] = source[x, y];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < secondHeight; y++)
					result[x, secondHeight + y] = second[x, y];

			return result;
		}

		/// <summary>
		/// ... This method is the generatilazed matrix multiplication.
		/// </summary>
		/// <typeparam name="T1"></typeparam>
		/// <typeparam name="T2"></typeparam>
		/// <typeparam name="TPartialResult"></typeparam>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="source"></param>
		/// <param name="second"></param>
		/// <param name="accumulator"></param>
		/// <param name="resultSelector"></param>
		/// <returns></returns>
		public static TResult[,] CrossJoin<T1, T2, TPartialResult, TResult>(this T1[,] source, T2[,] second,
			Func<T1, T2, TPartialResult> accumulator,
			Func<TPartialResult[], TResult> resultSelector
		)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			if (accumulator == null)
				throw new ArgumentNullException("accumulator");

			if (resultSelector == null)
				throw new ArgumentNullException("resultSelector");

			int firstWidth = source.GetLength(0), firstHeight = source.GetLength(1),
				secondWidth = second.GetLength(0), secondHeight = second.GetLength(1);

			if (firstHeight != secondWidth)
				throw new ArgumentException("The height of the first array must match the width of the second array.");

			TResult[,] result = new TResult[firstWidth, secondHeight];

			for (int x = 0; x < firstWidth; x++)
				for (int y = 0; y < secondHeight; y++)
				{
					TPartialResult[] partialResult = new TPartialResult[secondHeight];

					for (int i = 0; i < secondHeight; i++)
						partialResult[i] = accumulator(source[x, i], second[i, y]);

					result[x, y] = resultSelector(partialResult);
				}

			return result;
		}
		#endregion

		#region Rotating and flipping
		/// <summary>
		/// Rotates the elements of the array clockwise.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static T[,] RotateClockwise<T>(this T[,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			T[,] result = new T[height, width];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[height - y - 1, x] = source[x, y];

			return result;
		}

		/// <summary>
		/// Rotates the elements of the array counterclockwise.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static T[,] RotateCounterClockwise<T>(this T[,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			T[,] result = new T[height, width];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[y, width - x - 1] = source[x, y];

			return result;
		}

		/// <summary>
		/// Flips both dimensions of the array.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static T[,] FlipDiagonally<T>(this T[,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			T[,] result = new T[height, width];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[y, x] = source[x, y];

			return result;
		}

		/// <summary>
		/// Flips the elements of the array horizontally.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static T[,] FlipHorizontally<T>(this T[,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			T[,] result = new T[height, width];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[width - x - 1, y] = source[x, y];

			return result;
		}

		/// <summary>
		/// Flips the elements of the array vertically.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static T[,] FlipVertically<T>(this T[,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			T[,] result = new T[height, width];

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					result[x, height - y - 1] = source[x, y];

			return result;
		}
		#endregion

		#region Embedded arrays
		/// <summary>
		/// Maps a new array to each element and flattens the embedded arrays.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="selector">A map function.</param>
		/// <returns></returns>
		public static T[,] Expand<T>(this T[,] source, Func<T, T[,]> selector)
		{
			return source.Map(selector).Flatten();
		}

		/// <summary>
		/// Maps a single element to each embedded array.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="selector">A map function.</param>
		/// <returns></returns>
		public static T[,] Collapse<T>(this T[,][,] source, Func<T[,], T> selector)
		{
			return source.Map(selector);
		}

		/// <summary>
		/// Constructs a single array from the elements of the embedded arrays.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <returns></returns>
		public static T[,] Flatten<T>(this T[,][,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			int resultWidth = source.AsColumns().Sum(c => c.Max(e => e.GetLength(0))),
				resultHeight = source.AsRows().Sum(c => c.Max(e => e.GetLength(1)));

			T[,] result = new T[resultWidth, resultHeight];

			int offsetX = 0, offsetY = 0;
			int maxWidth = 0;

			for (int x = 0; x < width; x++)
			{
				offsetY = 0;
				maxWidth = 0;

				for (int y = 0; y < height; y++)
				{
					int subWidth = source[x, y].GetLength(0), subHeight = source[x, y].GetLength(1);

					for (int i = 0; i < subWidth; i++)
						for (int j = 0; j < subHeight; j++)
							result[offsetX + i, offsetY + j] = source[x, y][i, j];

					offsetY += subHeight;

					if (subWidth > maxWidth)
						maxWidth = subWidth;
				}

				offsetX += maxWidth;
			}

			return result;
		}
		#endregion

		#region Mathematics
		/// <summary>
		/// Returns the sum of the elements on the main diagonal.
		/// </summary>
		/// <param name="source"></param>
		/// <returns></returns>
		public static int Trace(this int[,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			if (width != height)
				throw new ArgumentOutOfRangeException("source", "Source must be a square matrix.");

			int result = 1;

			for (int i = 0; i < width; i++)
				result += source[i, i];

			return result;
		}
		/// <summary>
		/// Returns the sum of the elements on the main diagonal.
		/// </summary>
		/// <param name="source"></param>
		/// <returns></returns>
		public static double Trace(this double[,] source)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			int width = source.GetLength(0), height = source.GetLength(1);

			if (width != height)
				throw new ArgumentOutOfRangeException("source", "Source must be a square matrix.");

			double result = 1D;

			for (int i = 0; i < width; i++)
				result += source[i, i];

			return result;
		}
		#endregion

		/// <summary>
		/// Determines whether two arrays are equal by comparing their elements using an equality comparer.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The array to compare.</param>
		/// <param name="comparer">An equality comparer to use to compare elements.</param>
		/// <returns></returns>
		public static bool ArrayEqual<T>(this T[,] source, T[,] second, IEqualityComparer<T> comparer)
		{
			if (source == null)
				throw new ArgumentNullException("source");

			if (second == null)
				throw new ArgumentNullException("second");

			if (comparer == null)
				throw new ArgumentNullException("comparer");

			int width = source.GetLength(0), height = source.GetLength(1);

			for (int x = 0; x < width; x++)
				for (int y = 0; y < height; y++)
					if (!comparer.Equals(source[x, y], second[x, y]))
						return false;

			return true;
		}
		/// <summary>
		/// Determines whether two arrays are equal by comparing their elements using the default equality comparer.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="source"></param>
		/// <param name="second">The array to compare.</param>
		/// <returns></returns>
		public static bool ArrayEqual<T>(this T[,] source, T[,] second) where T : IEquatable<T>
		{
			return source.ArrayEqual(second, EqualityComparer<T>.Default);
		}
	}
}